// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use convert_case::{Case, Casing};
use proc_macro::TokenStream;
use syn::{Data, DeriveInput, Meta, NestedMeta};

#[proc_macro_derive(MsgTag, attributes(msg_tag))]
pub fn derive_msg_tag(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as DeriveInput);
    match &input.data {
        Data::Struct(_) => {
            let mut specified_tag = None;
            let mut specified_case = None;
            for attr in input.attrs.iter() {
                if !attr.path.is_ident("msg_tag") {
                    continue;
                }

                let meta = attr.parse_meta().expect("Failed to parse attribute");
                let meta_list = match meta {
                    Meta::List(meta_list) => meta_list,
                    _ => continue,
                };

                for meta in meta_list.nested.iter() {
                    match meta {
                        NestedMeta::Meta(meta) => {
                            match meta {
                                Meta::Path(_) => {},
                                Meta::NameValue(meta_name_value) => {
                                    if meta_name_value.path.is_ident("tag") {
                                        match &meta_name_value.lit {
                                            syn::Lit::Str(lit_str) =>  {
                                                let tag = lit_str.value();
                                                specified_tag = Some(tag);
                                            },
                                            _ => panic!("Expected string value for tag")
                                        }
                                    } else if meta_name_value.path.is_ident("case") {
                                        match &meta_name_value.lit {
                                            syn::Lit::Str(lit_str) =>  {
                                                let case_string = lit_str.value();
                                                let case = match case_string.as_str() {
                                                    "snake_case" => Case::Snake,
                                                    "PascalCase" | "UpperCamelCase" => Case::Pascal,
                                                    _ => panic!("Invalid case: {}", case_string),
                                                };
                                                specified_case = Some(case);
                                            },
                                            _ => panic!("Expected string value for case")
                                        }
                                    }
                                },
                                Meta::List(_) => {},
                            }
                        },
                        NestedMeta::Lit(_) => {},
                    }
                }
            }

            let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
            let ty = &input.ident;
            let tag = specified_tag.unwrap_or(
                ty.to_string().to_case(specified_case.unwrap_or(Case::Pascal))
            );

            quote::quote! {
                impl #impl_generics maestro_message::MsgTag for #ty #ty_generics #where_clause {
                    fn tag(&self) -> &'static str {
                        #tag
                    }
                }
            }.into()
        },
        _ => panic!("Only support struct"),
    }
}
